<?php

namespace yunj\core\builder;

use yunj\core\Config;
use yunj\core\Validate;
use yunj\core\enum\BuilderType;
use yunj\core\control\YunjControl;
use yunj\core\validate\YunjFormValidate;
use yunj\core\exception\GeneralException;

final class YunjForm extends Builder {

    protected $type = BuilderType::FORM;

    protected $builderValidateClass = YunjFormValidate::class;

    protected $scriptFileList = ["/static/yunj/js/form.min.js?v=" . YUNJ_VERSION];

    protected $options = ["tab", "field", "validate", "button", 'load', 'submit'];

    /**
     * 表单切换栏（默认选中第一项）
     * 'tab'=>[
     *      'base'=>'基础',
     *      'other'=>'其他'
     *  ]
     * @var array<key,title>
     */
    private $tab;

    /**
     * 表单字段
     * @var array<field-key,field-args>|array<tab-key,array<field-key,field-args>>
     * 示例：tab没有设置时   array<field-key,field-args>
     * 'field'=>[
     *      // 单行文本框
     *      'field_key'=>[
     *          'type'=>'text',         //（可选，默认text）
     *          'title'=>'field_label', //（可选）
     *          'placeholder'=>'',      //（可选）
     *          'value'=>'',            //默认值（可选）
     *      ],...
     *  ]
     * 示例：tab设置时    array<tab-key,array<field-key,field-args>>
     * 'field'=>[
     *      'base'=>[],     // 切换栏tab=base的筛选表单（可选）
     *      'other'=>[      // 切换栏tab=other的筛选表单（可选）
     *          // 单行文本框
     *          'field_key'=>[
     *              'type'=>'text',         //（可选，默认text）
     *              'title'=>'field_label', //（可选）
     *              'placeholder'=>'',      //（可选）
     *              'value'=>'',            //默认值（可选）
     *          ],...
     *      ]
     *  ]
     */
    private $field;

    /**
     * 表单按钮（默认为空）
     * 'button'=>['back','reload','clear','submit'=>['auth'=>'data_submit']]
     * @var array<string>
     */
    private $button;

    /**
     * 所有表单字段
     * 
     * @var array<field-key,field-args>
     */
    private $allField;

    /**
     * 表单load数据值
     * @var array
     */
    private $loadValues;

    /**
     * 表单submit数据值
     * @var array
     */
    private $submitValues;

    /**
     * 数据加载回调方法
     * @var callable
     */
    private $loadCallback;

    /**
     * 数据提交回调方法
     * @var callable
     */
    private $submitCallback;

    protected function setAttr(array $args = []): void {
        parent::setAttr($args);
        $this->config = Config::get('form.', []);
    }

    /**
     * Notes: 切换栏
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:05
     * @param callable|array<key,title>|string|number $code
     * callable:
     *      function(){
     *          return ["base"=>"基础","other"=>"其他"];
     *      }
     * array<key,title>:
     *      ["base"=>"基础","other"=>"其他"];
     * @param string $title
     * @return YunjForm
     */
    public function tab($code, string $title = '') {
        if ($this->existError()) return $this;
        if (!is_callable($code)) {
            $callable = function () use ($code, $title) {
                return is_array($code) ? $code : [$code => $title];
            };
        } else {
            $callable = $code;
        }
        $this->setOptionCallbale('tab', $callable);
        return $this;
    }

    /**
     * Notes: 执行切换栏回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:42
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_tab_callable(callable $callable) {
        $tab = $this->tab ? $this->tab : [];
        $result = call_user_func_array($callable, [$this]);
        if (!is_array($result)) return $this->error("YunjForm [tab] 需返回有效数组");
        $this->tab = $result + $tab;
        return $this;
    }

    /**
     * Notes: 判断是否设置tab
     * Author: Uncle-L
     * Date: 2022/3/8
     * Time: 17:30
     * @return bool
     */
    public function isSetTab(): bool {
        $this->execOptionsCallbale("tab");
        return $this->tab && count($this->tab) > 0;
    }

    /**
     * Notes: 表单字段
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:08
     * @param callable|array<field-key,field-args> $field
     * @return YunjForm
     */
    public function field($field) {
        if ($this->existError()) return $this;
        if (!is_callable($field)) {
            $callable = function () use ($field) {
                return $field;
            };
        } else {
            $callable = $field;
        }
        $this->setOptionCallbale('field', $callable);
        return $this;
    }

    /**
     * Notes: 执行表单字段回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_field_callable(callable $callable) {
        if ($this->existError()) return $this;
        $tabs = $this->tab;
        $field = $this->field ?: [];
        if ($tabs) {
            foreach ($tabs as $tab => $title) {
                $tabField = $callable($this, $tab);
                if (!is_array($tabField)) return $this->error("YunjForm [field] 需返回有效数组");
                $field[$tab] = $field[$tab] ?? [];
                $this->handleFieldTab($field[$tab], $tabField);
            }
        } else {
            $_field = $callable($this, null);
            if (!is_array($_field)) return $this->error("YunjForm [field] 需返回有效数组");
            $this->handleFieldTab($field, $_field);
        }
        $this->field = $field;
        return $this;
    }

    /**
     * Notes: 处理切换栏对应的表单字段
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:25
     * @param array $fieldTab
     * @param array $fields
     * @return YunjForm
     */
    protected function handleFieldTab(array &$fieldTab, array $fields) {
        foreach ($fields as $key => $field) {
            if (!$field || !is_array($field)) return $this->error("YunjForm [field] 返回字段[{$key}]异常");
            list($error, $args) = YunjControl::formField($field, $this->id);
            if ($error) return $this->error($error);
            if ($args) $fieldTab[$key] = $args;
        }
        return $this;
    }

    /**
     * Notes: 表单按钮
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:12
     * @param callable|array<string>|...string $button
     * callable:
     *      function(){
     *          return ["back","reset","submit"];
     *      }
     * array<string>:
     *      ["back","reset","submit"]
     * ...string:
     *      "back","reset","submit"
     * @return YunjForm
     */
    public function button(...$button) {
        if ($this->existError()) return $this;
        if (!$button) return $this;
        $btn0 = $button[0];
        if (is_callable($btn0)) {
            $callable = $btn0;
        } elseif (is_array($btn0)) {
            $callable = function () use ($btn0) {
                return $btn0;
            };
        } else {
            $callable = function () use ($button) {
                return $button;
            };
        }
        $this->setOptionCallbale('button', $callable);
        return $this;
    }

    /**
     * Notes: 执行表单按钮回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_button_callable(callable $callable) {
        if ($this->existError()) return $this;
        $button = $this->button ?: [];
        $_button = $callable($this);
        if (!is_array($button)) return $this->error("YunjForm [button] 需返回有效数组");
        foreach ($_button as $k => $v) {
            $key = is_string($v) ? $v : $k;
            list($error, $args) = YunjControl::formButton($key, $v);
            if ($error) return $this->error($error);
            if ($args) $button[$key] = $args;
        }
        $this->button = $button;
        return $this;
    }

    /**
     * Notes: 数据获取
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:45
     * @param callable $callable
     * @return YunjForm
     */
    public function load(callable $callable) {
        if ($this->existError()) return $this;
        $this->loadCallback = $callable;
        return $this;
    }

    /**
     * Notes: 数据提交
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:45
     * @param callable $callable
     * @return YunjForm
     */
    public function submit(callable $callable) {
        if ($this->existError()) return $this;
        $this->submitCallback = $callable;
        return $this;
    }

    /**
     * @return array
     * @throws GeneralException
     */
    public function viewArgs(): array {
        $args = parent::viewArgs();
        $tab = $this->tab;
        if ($tab) {
            $args['tab'] = $tab;
        }
        if ($field = $this->getField()) {
            $args['field'] = $field;
            // 过滤掉没有权限的数据
            $loadValues = $this->filterFieldValues($this->getLoadValues());
            $args['loadValues'] = $loadValues;
        }
        if ($this->button) {
            $args['button'] = $this->button;
        }
        return $args;
    }

    /**
     * Notes: 异步处理数据提交
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:37
     * @param $param
     * @return array
     */
    protected function async_submit($param) {
        if (!$this->submitCallback) throw_error_json("YunjForm [submit] 未设置");
        // 验证
        $this->validateCheck($param['data'], 'Submit');
        // 执行提交动作
        $submitValues = $param['data'];
        $this->submitValues = $submitValues;
        // 触发表单构建器提交操作前事件
        event('YunjFormSubmitBefore', ['builder' => $this, 'values' => $submitValues]);
        $response = call_user_func_array($this->submitCallback, [$this, $submitValues]);
        return $response;
    }


    /**
     * 验证器校验数据
     * @param array $data 待验证数据
     * @param string $scene
     */
    private function validateCheck(array &$data, string $scene): void {
        $fields = $this->getAllField();
        if (!$fields) return;
        // 过滤掉只读字段
        $_fields = [];
        $_data = [];
        foreach ($fields as $k => $v) {
            if ($v['readonly']) {
                continue;
            }
            $_fields[$k] = $v;
            if (array_key_exists($k, $data)) {
                $_data[$k] = $data[$k];
            }
        }
        $fields = $_fields;
        $data = $_data;

        parent::validateCheckByFieldsData($fields, $data, $scene);
    }

    /**
     * 获取表单字段
     * @return field-args[]|field-args[][]
     */
    public function getField(): array {
        if (!is_null($this->field)) {
            return $this->field;
        }
        $this->execOptionsCallbale("tab,field");
        if ($this->existError()) throw_error_json($this->getError());
        return $this->field ?? [];
    }

    /**
     * 获取表单所有字段配置key=>args
     * @return field-args[]|field-args[][]
     */
    public function getAllField(): array {
        if (!is_null($this->allField)) {
            return $this->allField;
        }
        $this->allField = [];
        $field = $this->getField();
        if ($this->isSetTab()) {
            foreach ($field as $tab => $tabField) {
                $this->allField += $tabField;
            }
        } else {
            $this->allField = $field;
        }
        return $this->allField;
    }

    /**
     * 获取当前表单load字段值
     * @return array
     */
    public function getLoadValues(): array {
        if (is_null($this->loadValues)) {
            $values = is_callable($this->loadCallback) ? call_user_func_array($this->loadCallback, [$this]) : [];
            if (!is_array($values)) {
                $this->error(is_string($values) ? $values : 'YunjForm [load] 闭包返回数据异常');
                return [];
            }
            $this->loadValues = $values;
        }
        return $this->loadValues;
    }

    /**
     * 获取当前表单submit字段值
     * @return array
     */
    public function getSubmitValues(): array {
        return $this->submitValues ?: [];
    }

    /**
     * 获取当前表单配置的字段值
     * @return array
     */
    public function getSubmitFieldValues(): array {
        $values = $this->getSubmitValues();
        return $this->filterFieldValues($values, function ($field, $fieldArgs, $values) {
            // 只读字段不反回
            return !$fieldArgs['readonly'];
        });
    }

    /**
     * 过滤出字段配置值
     * @param array $values
     * @param callable|null $filterCall
     * @return array
     */
    private function filterFieldValues(array $values, ?callable $filterCall = null): array {
        $filterValues = [];
        if (!$values) {
            return $filterValues;
        }
        $fields = $this->getAllField();
        $this->filterTabFieldValues($filterValues, $fields, $values, $filterCall);
        return $filterValues;
    }

    /**
     * 过滤出选项卡下配置值
     * @param array $filterValues
     * @param array $tabField
     * @param array $values
     * @param callable|null $filterCall
     */
    private function filterTabFieldValues(array &$filterValues, array $tabField, array $values, ?callable $filterCall = null) {
        foreach ($tabField as $k => $v) {
            // 不存在的字段，或不满足过滤条件的字段 不反回
            if (!array_key_exists($k, $values) || ($filterCall && !$filterCall($k, $v, $values))) continue;
            $filterValues[$k] = $values[$k];
        }
    }

}